/**
 * UGENE - Integrated Bioinformatics Tools.
 * Copyright (C) 2008-2020 UniPro <ugene@unipro.ru>
 * http://ugene.net
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 */

#include "SequenceSplitWorker.h"

#include <QScopedPointer>

#include <U2Core/AnnotationTableObject.h>
#include <U2Core/AppContext.h>
#include <U2Core/DNASequence.h>
#include <U2Core/FailTask.h>
#include <U2Core/MultiTask.h>
#include <U2Core/TaskSignalMapper.h>
#include <U2Core/TextUtils.h>
#include <U2Core/U1AnnotationUtils.h>
#include <U2Core/U2OpStatusUtils.h>
#include <U2Core/U2SafePoints.h>

#include <U2Designer/DelegateEditors.h>

#include <U2Lang/ActorPrototypeRegistry.h>
#include <U2Lang/BaseActorCategories.h>
#include <U2Lang/BasePorts.h>
#include <U2Lang/BaseSlots.h>
#include <U2Lang/BaseTypes.h>
#include <U2Lang/ConfigurationEditor.h>
#include <U2Lang/CoreLibConstants.h>
#include <U2Lang/WorkflowEnv.h>

#include "GenericReadActor.h"

namespace U2 {
namespace LocalWorkflow {

const QString SequenceSplitWorkerFactory::ACTOR("extract-annotated-sequence");

const static QString REGIONED_SEQ_TYPE("regioned.sequence");

const static QString TRANSLATE_ATTR("translate");
const static QString COMPLEMENT_ATTR("complement");
const static QString EXTEND_LEFT_ATTR("extend-left");
const static QString EXTEND_RIGHT_ATTR("extend-right");
const static QString GAP_LENGTH_ATTR("merge-gap-length");
const static QString SPLIT_ATTR("split-joined-annotations");

QString SequenceSplitPromter::composeRichDoc() {
    IntegralBusPort *input = qobject_cast<IntegralBusPort *>(target->getPort(BasePorts::IN_SEQ_PORT_ID()));
    Actor *seqProducer = input->getProducer(BaseSlots::DNA_SEQUENCE_SLOT().getId());
    QString unsetStr = "<font color='red'>" + tr("unset") + "</font>";
    QString seqProducerText = tr("from <u>%1</u>").arg(seqProducer ? seqProducer->getLabel() : unsetStr);

    //translate or not?
    bool translate = getParameter(TRANSLATE_ATTR).toBool();
    QString translateText;
    if (translate) {
        translateText = tr("%1 it if annotation marks translated subsequence, ")
                            .arg(getHyperlink(TRANSLATE_ATTR, tr("translate")));
    }

    //complement or not?
    bool complement = getParameter(COMPLEMENT_ATTR).toBool();
    QString complementText;
    if (complement) {
        complementText = tr("make it %1 if annotation is located on complement strand, ")
                             .arg(getHyperlink(COMPLEMENT_ATTR, "reverse-complement"));
    }

    //expand
    QString expandText;
    int expandLeft = getParameter(EXTEND_LEFT_ATTR).toInt();
    int expandRight = getParameter(EXTEND_RIGHT_ATTR).toInt();
    if (expandLeft) {
        expandText += tr("expand it to left with <u>%1</u>, ").arg(getHyperlink(EXTEND_LEFT_ATTR, expandLeft));
    }
    if (expandRight) {
        expandText += tr("expand it to right with <u>%1</u>").arg(getHyperlink(EXTEND_RIGHT_ATTR, expandRight));
    }
    if (!expandRight && expandLeft) {
        expandText.remove(expandText.size() - 1, 2);
    }

    //merge result
    QString doc = tr("Extract each annotated sequence region %5 %2 %3%4")
                      //.arg(filterText)
                      .arg(complementText)
                      .arg(translateText)
                      .arg(expandText)
                      .arg(seqProducerText);
    doc.remove(QRegExp("[\\,\\s]*$"));    //remove all commas and spaces from the end;
    doc.append(".");
    return doc;
}

void SequenceSplitWorker::init() {
    seqPort = ports.value(BasePorts::IN_SEQ_PORT_ID());
    outPort = ports.value(BasePorts::OUT_SEQ_PORT_ID());
}

Task *SequenceSplitWorker::tick() {
    if (seqPort->hasMessage()) {
        seqPort->getContext();
        Message inputMessage = getMessageAndSetupScriptValues(seqPort);
        if (inputMessage.isEmpty()) {
            outPort->transit();
            return NULL;
        }
        cfg.translate = actor->getParameter(TRANSLATE_ATTR)->getAttributeValue<bool>(context);
        cfg.complement = actor->getParameter(COMPLEMENT_ATTR)->getAttributeValue<bool>(context);
        cfg.extLeft = actor->getParameter(EXTEND_LEFT_ATTR)->getAttributeValue<int>(context);
        cfg.extRight = actor->getParameter(EXTEND_RIGHT_ATTR)->getAttributeValue<int>(context);
        cfg.gapLength = actor->getParameter(GAP_LENGTH_ATTR)->getAttributeValue<int>(context);
        cfg.splitJoined = actor->getParameter(SPLIT_ATTR)->getAttributeValue<bool>(context);
        cfg.gapSym = '-';    //FIXME
        QVariantMap qm = inputMessage.getData().toMap();

        SharedDbiDataHandler seqId = qm.value(BaseSlots::DNA_SEQUENCE_SLOT().getId()).value<SharedDbiDataHandler>();
        QScopedPointer<U2SequenceObject> seqObj(StorageUtils::getSequenceObject(context->getDataStorage(), seqId));
        CHECK(NULL != seqObj.data(), NULL);
        U2OpStatusImpl os;
        DNASequence inputSeq = seqObj->getWholeSequence(os);
        CHECK_OP(os, new FailTask(os.getError()));

        inputAnns = StorageUtils::getAnnotationTable(context->getDataStorage(), qm[BaseSlots::ANNOTATION_TABLE_SLOT().getId()]);

        bool noSeq = inputSeq.isNull();
        bool noAnns = inputAnns.isEmpty();
        if (noSeq || noAnns) {
            if (noSeq) {
                coreLog.info(tr("No sequence provided to split worker"));
            } else {
                coreLog.info(tr("Nothing to extract. Sequence '%1' has no annotations.").arg(inputSeq.getName()));
            }

            if (seqPort->isEnded()) {
                outPort->setEnded();
            }
            return NULL;
        }

        ssTasks.clear();

        foreach (const SharedAnnotationData &ann, inputAnns) {
            Task *t = new ExtractAnnotatedRegionTask(inputSeq, ann, cfg);
            ssTasks.push_back(t);
        }
        if (ssTasks.isEmpty()) {
            return new FailTask(tr("Nothing to extract: no sequence region match the constraints"));
        }

        Task *t = new MultiTask("Sequence split tasks", ssTasks);
        connect(new TaskSignalMapper(t), SIGNAL(si_taskFinished(Task *)), SLOT(sl_onTaskFinished(Task *)));
        return t;
    } else if (seqPort->isEnded()) {
        setDone();
        outPort->setEnded();
    }
    return NULL;
}

void SequenceSplitWorker::cleanup() {
}

void SequenceSplitWorker::sl_onTaskFinished(Task *) {
    QVariantMap channelContext = outPort->getContext();
    int metadataId = outPort->getContextMetadataId();
    foreach (Task *t, ssTasks) {
        ExtractAnnotatedRegionTask *ssT = qobject_cast<ExtractAnnotatedRegionTask *>(t);
        SAFE_POINT(ssT, "Finished task 'ExtractAnnotatedRegionTask' is NULL", );
        int seqCount = 1;
        QList<DNASequence> sequences = ssT->getResultedSequences();
        QList<DNASequence>::Iterator iter(sequences.begin());
        for (; iter != sequences.end(); iter++) {
            DNASequence &resSeq = *iter;
            QString name = resSeq.getName() + " " + U1AnnotationUtils::buildLocationString(ssT->getInputAnnotation()->getRegions()) + " " + ssT->getInputAnnotation()->name;
            if (sequences.size() > 1) {
                name += " " + QString::number(seqCount++);
            }
            resSeq.info[DNAInfo::ID] = name;

            QVariantMap messageData;
            SharedDbiDataHandler seqId = context->getDataStorage()->putSequence(resSeq);
            messageData[BaseSlots::DNA_SEQUENCE_SLOT().getId()] = qVariantFromValue<SharedDbiDataHandler>(seqId);

            DataTypePtr messageType = WorkflowEnv::getDataTypeRegistry()->getById(REGIONED_SEQ_TYPE);
            if (outPort) {
                outPort->setContext(channelContext, metadataId);
                outPort->put(Message(messageType, messageData));
            }
        }
    }
    if (seqPort->isEnded()) {
        outPort->setEnded();
    }
}

void SequenceSplitWorkerFactory::init() {
    QList<PortDescriptor *> portDescs;
    QList<Attribute *> attribs;

    //accept sequence and annotated regions as input
    QMap<Descriptor, DataTypePtr> inputMap;
    inputMap[BaseSlots::DNA_SEQUENCE_SLOT()] = BaseTypes::DNA_SEQUENCE_TYPE();
    inputMap[BaseSlots::ANNOTATION_TABLE_SLOT()] = BaseTypes::ANNOTATION_TABLE_TYPE();

    QMap<Descriptor, DataTypePtr> outMap;
    outMap[BaseSlots::DNA_SEQUENCE_SLOT()] = BaseTypes::DNA_SEQUENCE_TYPE();

    DataTypePtr inSet(new MapDataType(Descriptor(REGIONED_SEQ_TYPE), inputMap));
    DataTypeRegistry *dr = WorkflowEnv::getDataTypeRegistry();
    assert(dr);
    dr->registerEntry(inSet);

    DataTypePtr outSet(new MapDataType(Descriptor(REGIONED_SEQ_TYPE), outMap));
    dr->registerEntry(outSet);

    {    //Create input port descriptors
        Descriptor seqDesc(BasePorts::IN_SEQ_PORT_ID(), SequenceSplitWorker::tr("Input sequence"), SequenceSplitWorker::tr("A sequence which will be split into annotated regions."));
        Descriptor outDesc(BasePorts::OUT_SEQ_PORT_ID(), SequenceSplitWorker::tr("Annotated regions"), SequenceSplitWorker::tr("Resulted subsequences, translated and complemented according to corresponding annotations."));

        portDescs << new PortDescriptor(seqDesc, inSet, /*input*/ true);
        portDescs << new PortDescriptor(outDesc, outSet, /*input*/ false, /*multi*/ true);
    }

    {    //Create attributes descriptors
        Descriptor translateDesc(TRANSLATE_ATTR,
                                 SequenceSplitWorker::tr("Translate"),
                                 SequenceSplitWorker::tr("Translate the annotated regions."));
        Descriptor complementDesc(COMPLEMENT_ATTR,
                                  SequenceSplitWorker::tr("Complement"),
                                  SequenceSplitWorker::tr("Complement the annotated regions if the corresponding annotation is located on complement strand."));
        Descriptor splitDesc(SPLIT_ATTR,
                             SequenceSplitWorker::tr("Split joined"),
                             SequenceSplitWorker::tr("Split joined annotations to single region annotations."));
        Descriptor extendLeftDesc(EXTEND_LEFT_ATTR,
                                  SequenceSplitWorker::tr("Extend left"),
                                  SequenceSplitWorker::tr("Extend the resulted regions to left."));
        Descriptor extendRightDesc(EXTEND_RIGHT_ATTR,
                                   SequenceSplitWorker::tr("Extend right"),
                                   SequenceSplitWorker::tr("Extend the resulted regions to right."));
        Descriptor gapLengthDesc(GAP_LENGTH_ATTR,
                                 SequenceSplitWorker::tr("Gap length"),
                                 SequenceSplitWorker::tr("Insert gap of specified length between merged locations of annotation."));

        attribs << new Attribute(translateDesc, BaseTypes::BOOL_TYPE(), /*required*/ false, QVariant(false));
        attribs << new Attribute(complementDesc, BaseTypes::BOOL_TYPE(), /*required*/ false, QVariant(false));
        attribs << new Attribute(splitDesc, BaseTypes::BOOL_TYPE(), /*required*/ false, QVariant(false));
        attribs << new Attribute(extendLeftDesc, BaseTypes::NUM_TYPE(), /*required*/ false, QVariant(0));
        attribs << new Attribute(extendRightDesc, BaseTypes::NUM_TYPE(), /*required*/ false, QVariant(0));
        attribs << new Attribute(gapLengthDesc, BaseTypes::NUM_TYPE(), false, QVariant(0));
    }

    Descriptor desc(SequenceSplitWorkerFactory::ACTOR,
                    SequenceSplitWorker::tr("Get Sequences by Annotations"),
                    SequenceSplitWorker::tr("Creates sequences from annotated regions of input sequence."));
    ActorPrototype *proto = new IntegralBusActorPrototype(desc, portDescs, attribs);

    //create delegates for attribute editing
    QMap<QString, PropertyDelegate *> delegates;
    {
        QVariantMap eMap;
        eMap["minimum"] = (0);
        eMap["maximum"] = (INT_MAX);
        delegates[EXTEND_LEFT_ATTR] = new SpinBoxDelegate(eMap);
        delegates[EXTEND_RIGHT_ATTR] = new SpinBoxDelegate(eMap);
        delegates[GAP_LENGTH_ATTR] = new SpinBoxDelegate(eMap);
    }

    proto->setEditor(new DelegateEditor(delegates));

    //    proto->setIconPath("");
    proto->setPrompter(new SequenceSplitPromter());
    WorkflowEnv::getProtoRegistry()->registerProto(BaseActorCategories::CATEGORY_BASIC(), proto);

    DomainFactory *localDomain = WorkflowEnv::getDomainRegistry()->getById(LocalDomainFactory::ID);
    localDomain->registerEntry(new SequenceSplitWorkerFactory());
}

Worker *SequenceSplitWorkerFactory::createWorker(Actor *a) {
    return new SequenceSplitWorker(a);
}

}    // namespace LocalWorkflow
}    // namespace U2
